package com.bomberdroid.ui;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.Toast;

import com.bomberdroid.R;
import com.bomberdroid.protocols.ConnectedThread;
import com.bomberdroid.protocols.Connexion;
import com.bomberdroid.protocols.DataSharing;

public class Server extends Activity {

	private ListView server_listView;

	private Connexion connexion;
	private BluetoothServerSocket server;
	private boolean stopListening = false;
	private ArrayList<ConnectedThread> clients = new ArrayList<ConnectedThread>();
	private ArrayAdapter<String> clientsList;
	private Spinner spinner;
	private ArrayAdapter<String> adapter;

	// Handler to manage communications from clients
	private Handler.Callback c = new Handler.Callback() {
		public boolean handleMessage(Message msg) {
			return handleCommunication(msg);
		}
	};
	private Handler handler = new Handler(c);

	// See internal class @ end of file
	private ListenClientsTask listenClientsTask = new ListenClientsTask();

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		this.setContentView(R.layout.server);

		spinner = (Spinner) findViewById(R.id.mapList);
		List<String> list = new ArrayList<String>();

		Field[] fields = R.raw.class.getFields();
		for (int count = 0; count < fields.length; count++) {
			list.add(fields[count].getName());
		}

		adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, list);

		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
		spinner.setAdapter(adapter);

		this.clientsList = new ArrayAdapter<String>(this, android.R.layout.test_list_item);
		this.server_listView = (ListView) findViewById(R.id.server_listView);
		this.server_listView.setAdapter(clientsList);

		this.connexion = new Connexion(this);

		// If the bluetooth is enabled, then we request the Device to be
		// Discoverable
		// Otherwise we wait for the "onActivityResult()" method (return of
		// checkBluetooth()) to do it
		if (this.connexion.checkBluetooth()) {
			this.requestDiscoverable();
		}
	}

	@Override protected void onRestart() {
		super.onRestart();
		for(ConnectedThread client : this.clients)
			client.setHandler(this.handler);
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);

		switch (requestCode) {
		case Connexion.REQUEST_ENABLE_BT:
			if (resultCode == RESULT_OK) {
				this.requestDiscoverable();
			} else {
				Toast.makeText(getApplicationContext(), "Bluetooth required.", Toast.LENGTH_LONG).show();
				this.finish();
			}
			break;
		case Connexion.REQUEST_DISCOVERABLE_BT:
			if (resultCode != RESULT_CANCELED) {
				this.listenClientsTask.execute();
				this.updateList();
			} else {
				Toast.makeText(getApplicationContext(), "Discoverable state required.", Toast.LENGTH_LONG).show();
				this.finish();
			}
			break;
		}
	}

	/**
	 * Waits for clients to connect on the server and add them to the clients
	 * ArrayList
	 */
	private void requestDiscoverable() {
		if (!this.connexion.checkBluetooth())
			return;

		// Request discoverable
		Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
		discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
		this.startActivityForResult(discoverableIntent, Connexion.REQUEST_DISCOVERABLE_BT);
	}

	/** Adds the client to the list */
	private void addClient(BluetoothSocket socket) {
		ConnectedThread newClient = new ConnectedThread(socket, this.handler);
		newClient.start();
		// We add the client to the list
		this.clients.add(newClient);
		// We send the list of players to the new Client
		String mess = "b;" + this.connexion.getIdentity() + "&";
		for (ConnectedThread client : this.clients) {
			mess += "b;" + client.getIdentity() + "&";
		}
		try{newClient.write(mess);}catch(Exception e){newClient.cancel();this.clients.remove(newClient);return;}
		// We notify the other clients of a new client (but we don't send the
		// information to the new Client
		this.broadCast("2;" + newClient.getIdentity(), newClient);
		this.updateList();
	}

	/** Remove the client from the list */
	private void removeClient(String identity) {
		ConnectedThread leavingClient = null;
		for (ConnectedThread client : this.clients) {
			if (client.getIdentity().equals(identity))
				leavingClient = client;
		}
		if (leavingClient == null) {
			Log.d("Communication", "Error : Client " + identity + " not found");
			return;
		}
		this.clients.remove(leavingClient);
		// We notify the other clients
		this.broadCast("c;" + leavingClient.getIdentity(), null);
		leavingClient.cancel();
		this.updateList();
	}

	/** Updates the list with the connected devices */
	private void updateList() {
		this.clientsList.clear();
		this.clientsList.add(this.connexion.getIdentity());
		for (ConnectedThread client : this.clients) {
			this.clientsList.add(client.getIdentity());
		}
	}

	/** Send a message to every connected clients except the one in parameters
	 * @param msg
	 * @param clientExcept will not receive the message, null if no exception
	 */
	private void broadCast(String msg, ConnectedThread clientExcept) {
		for (ConnectedThread client : this.clients) {
			if (client != clientExcept)
				try{client.write(msg);}catch(Exception e){client.cancel();this.clients.remove(client);}
		}
	}

	/**
	 * Handles messages from clients comming through ConnectedThread and the Handler
	 * @param msg
	 * @return
	 */
	private boolean handleCommunication(Message msg) {
		boolean res = true;
		String s = new String((byte[]) msg.obj, 0, msg.arg1);
		Log.d("Communication", "Server received : " + s);
		String[] messages = s.split("&");
		for (String mess : messages) {
			String[] data = mess.split(";");
			if (data[0].equals("c")) {
				this.removeClient(data[1]);
			} else {
				res = false;
			}
		}
		return res;
	}

	/** Starts the server (currently useless) */
	public void clickButtonStart(View v) {

		String StingMap = String.valueOf(spinner.getSelectedItem());
		int idMap = getResources().getIdentifier(StingMap, "raw", getPackageName());

		DataSharing.Server_clients = this.clients;
		DataSharing.Server_connexion = this.connexion;
		DataSharing.Server_map = idMap;
		
		this.stopListening = true;

		this.broadCast("a;" + DataSharing.Server_map, null);

		Intent myIntent = new Intent(this, ServerGame.class);
		startActivity(myIntent);
	}

	/** Cleans the Bluetooth related objects before closing the Activity */
	@Override
	protected void onDestroy() {
		Log.d("Communication", "Server : Destroying");
		this.stopListening = true;
		try {this.server.close();} catch (Exception e) {e.printStackTrace();}
		for (ConnectedThread client : this.clients) {
			try {client.write("z");} catch (Exception e) {e.printStackTrace();}
			try {client.cancel();} catch (Exception e) {e.printStackTrace();}
		}
		super.onDestroy();
	}

	/**
	 * internal class used to listen for clients in background and updating the
	 * listView
	 */
	private class ListenClientsTask extends AsyncTask<Void, BluetoothSocket, Void> {

		@Override
		protected Void doInBackground(Void... params) {
			// Preparing the server, if it fails then we finish the Activity
			try {
				server = connexion.getBluetoothAdapter().listenUsingRfcommWithServiceRecord("bomberDroid", Connexion.uuid);
				while (!stopListening) {
					try {
						publishProgress(server.accept(2000));
					} catch (IOException e) {
					}
				}
				server.close();
			} catch (Exception e) {
				e.printStackTrace();
				finish();
			}
			return null;
		}

		@Override
		protected void onProgressUpdate(BluetoothSocket... values) {
			addClient(values[0]);
			super.onProgressUpdate(values);
		}

	}

}
